use std::collections::HashMap;
use std::collections::LinkedList;
use std::hash::Hash;
use std::sync::Arc;

mod timeoutset;

pub use timeoutset::TimeoutSet;

pub enum MemCacheMode {
    None,
    TryClearAtUpdate,
}

impl MemCacheMode {
    pub fn is_try_clear(&self) -> bool {
        match self {
            MemCacheMode::TryClearAtUpdate => true,
            _ => false,
        }
    }
}

impl Default for MemCacheMode {
    fn default() -> Self {
        Self::TryClearAtUpdate
    }
}

#[derive(Default)]
pub struct MemCache<K, T>
where
    K: Eq + Hash,
{
    map: HashMap<K, (u64, T)>,
    time_set: TimeoutSet<K>,
    last_clear_time: u64,
    try_clear_interval: u64,
    pub mode: MemCacheMode,
    pub time_out_fn: Option<Arc<dyn Fn(K, T) + Send + Sync>>,
}

fn now_millis() -> u64 {
    use std::time::SystemTime;
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap()
        .as_millis() as u64
}

impl<K, T> MemCache<K, T>
where
    K: Eq + Hash + Clone,
    T: Clone,
{
    pub fn new() -> Self {
        Self::new_with_clear_interval(10000)
    }
    pub fn new_with_clear_interval(interval_millis: u64) -> Self {
        Self {
            map: Default::default(),
            time_set: Default::default(),
            last_clear_time: 0u64,
            try_clear_interval: interval_millis,
            mode: MemCacheMode::TryClearAtUpdate,
            time_out_fn: None,
        }
    }

    fn build_last_time(cache_sec: i32) -> u64 {
        if cache_sec == -1i32 {
            0
        } else {
            ((cache_sec * 1000) as u64) + now_millis()
        }
    }

    fn try_clear(&mut self) {
        if !self.mode.is_try_clear() {
            return;
        }
        let current_time = now_millis();
        if current_time - self.try_clear_interval > self.last_clear_time {
            self.clear_time_out();
        }
    }

    pub fn get_time_out_keys(&self) -> LinkedList<&K> {
        let current_time = now_millis();
        self.time_set.get_timeout_values(current_time)
    }

    pub fn clear_time_out(&mut self) {
        let current_time = now_millis();
        let list = self.time_set.timeout(current_time);
        for item in list {
            if let Some((t, _)) = self.map.get(&item) {
                if *t > current_time {
                    continue;
                }
                let (_, v) = self.map.remove(&item).unwrap();
                if let Some(f) = &self.time_out_fn {
                    f(item, v);
                }
            }
        }
        self.last_clear_time = current_time;
    }

    pub fn set(&mut self, key: K, val: T, cache_sec: i32) {
        self.try_clear();
        if cache_sec == 0i32 || cache_sec < -1i32 {
            return;
        }
        let last_time = Self::build_last_time(cache_sec);
        self.time_set.add(last_time, key.clone());
        self.map.insert(key, (last_time, val));
    }

    pub fn update_time_out(&mut self, key: &K, cache_sec: i32) {
        self.try_clear();
        if cache_sec == 0i32 || cache_sec < -1i32 {
            return;
        }
        match self.get(key) {
            Ok(_) => {
                let last_time = Self::build_last_time(cache_sec);
                self.time_set.add(last_time, key.clone());
            }
            Err(_) => {}
        };
    }

    pub fn get(&self, key: &K) -> Result<T, String> {
        match self.map.get(key) {
            Some((t, v)) => {
                if *t == 0 {
                    return Ok(v.clone());
                } else if now_millis() > *t {
                    return Err("Expried".to_owned());
                }
                Ok(v.clone())
            }
            None => Err("NOT FOUND".to_owned()),
        }
    }

    /// return the key value time to live;
    /// The unit of time returned is seconds
    pub fn time_to_live(&self, key: &K) -> i32 {
        match self.map.get(key) {
            Some((t, _)) => {
                let now = now_millis();
                if *t > now {
                    ((*t - now) / 1000) as i32
                } else {
                    -1
                }
            }
            None => -1,
        }
    }

    pub fn remove(&mut self, key: &K) -> Option<T> {
        self.try_clear();
        match self.map.remove(key) {
            Some((_, v)) => Some(v),
            _ => None,
        }
    }

    pub fn len(&self) -> usize {
        self.time_set.len()
    }

    pub fn item_size(&self) -> usize {
        self.time_set.item_size()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    fn time_out_print(k: String, v: String) {
        println!("============ time out,{}:{}", &k, &v);
    }

    #[test]
    fn test01() {
        let mut m = MemCache::new_with_clear_interval(100);
        //m.time_out_fn = Some(Arc::new(time_out_print));
        let ref_info = Arc::new(1);
        m.time_out_fn = Some(Arc::new(move |k, v| {
            println!("============ time out,{}:{}", &k, &v);
            println!("ref_info ,{}", &ref_info)
        }));
        let name = "name".to_owned();
        m.set("name".to_owned(), "abc".to_owned(), 1);
        println!("1.{:?}", m.get(&name));
        assert!(m.get(&name).is_ok());
        std::thread::sleep(Duration::from_millis(1001));
        println!("2:{:?}", m.get(&name));
        assert!(m.get(&name).is_err());
        m.set("name".to_owned(), "abc".to_owned(), 1);
        println!("3:{:?}", m.get(&name));
        assert!(m.get(&name).is_ok());
        std::thread::sleep(Duration::from_millis(1001));
        assert!(m.get(&name).is_err());
        println!("4:{:?}", m.get(&name));
        std::thread::sleep(Duration::from_millis(1001));
        m.clear_time_out()
    }
}
